All files / src/utils device.ts

0% Statements 0/82
0% Branches 0/62
0% Functions 0/9
0% Lines 0/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132                                                                                                                                                                                                                                                                       
/**
 * Browser device fingerprint utilities for stable device registration in the frontend.
 * Stores a stable device_id and a human-friendly device_name in localStorage.
 */
 
function safeLocalStorageGet(key: string): string | null {
  try {
    if (typeof window === 'undefined') return null;
    return window.localStorage.getItem(key);
  } catch {
    return null;
  }
}
 
function safeLocalStorageSet(key: string, value: string): void {
  try {
    if (typeof window === 'undefined') return;
    window.localStorage.setItem(key, value);
  } catch {
    /* ignore */
  }
}
 
function detectOS(): string {
  if (typeof navigator === 'undefined') return 'Unknown OS';
  const ua = navigator.userAgent || '';
  const platform = (navigator as any).platform || '';
 
  if (/android/i.test(ua)) return 'Android';
  if (/iphone|ipad|ipod/i.test(ua)) return 'iOS';
  if (/win/i.test(platform)) return 'Windows';
  if (/mac/i.test(platform)) return 'macOS';
  if (/linux/i.test(platform)) return 'Linux';
  return 'Unknown OS';
}
 
function detectBrowser(): string {
  if (typeof navigator === 'undefined') return 'Browser';
  const ua = navigator.userAgent || '';
 
  if (/edg/i.test(ua)) return 'Edge';
  if (/chrome|crios/i.test(ua) && !/edg/i.test(ua)) return 'Chrome';
  if (/firefox|fxios/i.test(ua)) return 'Firefox';
  if (/safari/i.test(ua) && !/chrome|crios/i.test(ua)) return 'Safari';
  if (/opr\//i.test(ua)) return 'Opera';
  return 'Browser';
}
 
function defaultDeviceName(): string {
  const os = detectOS();
  const br = detectBrowser();
  return `${os} ยท ${br}`;
}
 
function generateDeviceId(): string {
  try {
    if (typeof crypto !== 'undefined' && (crypto as any).randomUUID) {
      return (crypto as any).randomUUID();
    }
  } catch {
    // ignore
  }
 
  // Fallback: pseudo-random plus weak fingerprint
  try {
    const pieces: string[] = [];
    if (typeof navigator !== 'undefined') {
      pieces.push(navigator.userAgent || '');
      pieces.push((navigator as any).platform || '');
      pieces.push((navigator as any).language || '');
    }
    if (typeof screen !== 'undefined') {
      pieces.push(String(screen.width));
      pieces.push(String(screen.height));
      pieces.push(String((screen as any).colorDepth || ''));
    }
    pieces.push(String(Date.now()));
    const raw = pieces.join('|');
 
    // Simple hash
    let hash = 0;
    for (let i = 0; i < raw.length; i++) {
      hash = (hash << 5) - hash + raw.charCodeAt(i);
      hash |= 0;
    }
    const rand = Math.floor(Math.random() * 1e9).toString(36);
    return `web-${Math.abs(hash).toString(36)}-${rand}`;
  } catch {
    return `web-${Date.now().toString(36)}`;
  }
}
 
const STORAGE_KEYS = {
  DEVICE_ID: 'iptv_device_id',
  DEVICE_NAME: 'iptv_device_name'} as const;
 
export function getOrCreateBrowserDevice(): { id: string; name: string } {
  // 1) Try to read existing
  let id = safeLocalStorageGet(STORAGE_KEYS.DEVICE_ID);
  let name = safeLocalStorageGet(STORAGE_KEYS.DEVICE_NAME);
 
  // 2) Create new if missing
  if (!id) {
    id = generateDeviceId();
    safeLocalStorageSet(STORAGE_KEYS.DEVICE_ID, id);
  }
 
  if (!name) {
    name = defaultDeviceName();
    safeLocalStorageSet(STORAGE_KEYS.DEVICE_NAME, name);
  }
 
  return { id, name };
}
 
/**
 * Update the stored device name (user-customizable).
 */
export function setBrowserDeviceName(newName: string): void {
  const name = String(newName || '').trim() || defaultDeviceName();
  safeLocalStorageSet(STORAGE_KEYS.DEVICE_NAME, name);
}
 
/**
 * Force a new device id (not recommended, but available if needed).
 * Useful for debugging; production flows should keep device ID stable per browser profile.
 */
export function resetBrowserDeviceId(): string {
  const id = generateDeviceId();
  safeLocalStorageSet(STORAGE_KEYS.DEVICE_ID, id);
  return id;
}